/* ==========================================================================
 * MATLAB C++ matfile generator class v0.1
 * ==========================================================================
 * Copyright (C) 2013 - Ericson Norberto A Santos (ericsonnorberto@gmail.com)
 * Federal University Of Campina Grande - UFCG
 * 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Based on "Portable ANSI C MATLAB file exporter" by Malcolm McLean
 * http://www.malcolmmclean.site11.com/www/MatlabFiles/matfiles.html
 * Copyright (c) Malcolm McLean
*/
#include "MAT_file.hpp"

MAT_file::MAT_file()
{
}

MAT_file::~MAT_file()
{
    if (mat_filename.is_open())
    {
        mat_filename.close();
    }
}

int MAT_file::MATLAB_new_mat_file(const string& filename)
{
    int data_return = 0;
    
    
    /* Open file stream for output */
    mat_filename.open(filename.c_str(), ios::out | ios::binary);
    
    /* Check if file is open */
    if (!mat_filename.is_open())
    {
        data_return = -2;
    }
    else
    {
        /* Write header file identification */
        mat_filename << "MATLAB 5.0 MAT-file, Platform: PCWIN, Created on: ";
        mat_filename << MATLAB_get_date_time_system().substr(0, MATLAB_get_date_time_system().size()-1);

        /* All spaces indicate that there is no subsystems-specific data stored */
        for (int j=0; j<50; j++)
        {
            mat_filename << " ";
        }

        /* Endian indicator */
        int  end_version, data_endian;
        
        /* MAT-file version (2 bytes) */
        end_version = 0x0100;
        mat_filename.write(reinterpret_cast<char*>(&end_version), 2);
        
        /* Endian indicator (1 bytes each) */
        data_endian = 'I';
        mat_filename.write(reinterpret_cast<char*>(&data_endian), 1);
        data_endian = 'M';
        mat_filename.write(reinterpret_cast<char*>(&data_endian), 1);
    }
    
    return data_return;
}

bool MAT_file::MATLAB_file_exists(const string& filename)
{
    struct stat info;
    int ret = -1;

    ret = stat(filename.c_str(), &info);
    if (ret == 0) 
    {
        return true;
    } 
    else 
    {
        return false;
    }
}

string MAT_file::MATLAB_get_date_time_system()
{
    string data_return;
    time_t rawtime;
    
    struct tm * timeinfo;
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    
    data_return = asctime(timeinfo);
    
    return data_return;
}

int MAT_file::MATLAB_write_single_data_string(const string& s_tag, const string& d_value)
{
    int data_size = 0;
    int i, j;
    
    /* Matrix tag */
    data_size = 32 + 8 + s_tag.size() + 8 + MATLAB_round8(d_value.size()*2);
    if (s_tag.size() % 8)
    {
        data_size += 8 - (s_tag.size() % 8);
    }
    
    /* Write tag info */
    MATLAB_fput32le(miMATRIX);
    MATLAB_fput32le(data_size);
    
    /* Write class */
    MATLAB_fput32le(miUINT32);
    MATLAB_fput32le(8);
    mat_filename.put(mxCHAR_CLASS);
    mat_filename.put(0);
    mat_filename.put(0);
    mat_filename.put(0);
    MATLAB_fput32le(0);
    
    /* Matrix dimensions */
    MATLAB_fput32le(miUINT32);
    MATLAB_fput32le(8);
    MATLAB_fput32le(1);
    MATLAB_fput32le(d_value.size());
    
    /* Array name */
    MATLAB_fput32le(miUINT8);
    MATLAB_fput32le(s_tag.size());
    for (i=0; (i<s_tag.size()); i++)
    {
        mat_filename.put(s_tag.at(i));
    }
    while (i % 8)
    {
        mat_filename.put(0);
        i++;
    }
    
    /* Cell length and data type */
    MATLAB_fput32le(miUINT16);
    MATLAB_fput32le(d_value.size()*2);
    
    /* The actual string */
    for (i=0; (i<d_value.size()); i++)
    {
        MATLAB_fput16le(d_value.at(i));
    }
    
    /* Size of string element */
    j = d_value.size();
    while (j % 4)
    {
        MATLAB_fput16le(0);
        j++;
    }
    
    return EXIT_SUCCESS;
}

int MAT_file::MATLAB_write_matrix_data(const string& s_tag, double *d_value, int n_row, int n_col, int d_transpose)
{
    int data_size = 0;
    int i, j;
    
    /* Matrix tag */
    data_size = 16 + s_tag.size() + (n_row*n_col*8) + 32;
    if (s_tag.size() % 8)
    {
        data_size += 8 - (s_tag.size() % 8);
    }
    
    /* Write tag info */
    MATLAB_fput32le(miMATRIX);
    MATLAB_fput32le(data_size);
    
    /* Array descriptor field */
    MATLAB_fput32le(miUINT32);
    MATLAB_fput32le(8);
    mat_filename.put(mxDOUBLE_CLASS);
    
    /* Array flags */
    mat_filename.put(4);
    mat_filename.put(0);
    mat_filename.put(0);
    MATLAB_fput32le(0);
    
    /* Array dimensions */
    MATLAB_fput32le(miINT32);
    MATLAB_fput32le(8);
    MATLAB_fput32le(n_row);
    MATLAB_fput32le(n_col);

    /* Array name */
    MATLAB_fput32le(miINT8);
    MATLAB_fput32le(s_tag.size());
    for (i=0; (i<s_tag.size()); i++)
    {
        mat_filename.put(s_tag.at(i));
    }
    while (i%8)
    {
        mat_filename.put(0);
        i++;
    }

    /* The actual data */
    MATLAB_fput32le(miDOUBLE);
    MATLAB_fput32le(n_row*n_col*8);
    if (d_transpose)
    {
        for (i=0; i<n_row; i++)
        {
            for (j=0; j<n_col; j++)
            {
                MATLAB_fwriteieee754(d_value[i*n_col+j], 0);
            }
        }
    }
    else
    {
        for (i=0; i<n_col; i++)
        {
            for (j=0; j<n_row; j++)
            {
                MATLAB_fwriteieee754(d_value[j*n_col+i], 0);
            }
        }
    }
    
    return EXIT_SUCCESS;
}

/* Round up to multiple of 8 */
int MAT_file::MATLAB_round8(int d_value)
{
   if (d_value % 8)
   {
       return d_value + 8 - (d_value % 8);
   }
   else
   {
       return d_value;
   }
}

/* Write a 16-bit little-endian integer to file */
int MAT_file::MATLAB_fput16le(int d_value)
{
    mat_filename.put(d_value & 0xFF);
    mat_filename.put((d_value >> 8) & 0xFF);
    
    return EXIT_SUCCESS;
}

/* Put a 32 bit little-endian integer to file */
int MAT_file::MATLAB_fput32le(int d_value)
{
    for (int i=0; i<4; i++)
    {
        mat_filename.put(d_value & 0xFF);
        d_value >>= 8;
    }
    
    return EXIT_SUCCESS;
}

/*
 * write a double to a stream in ieee754 format regardless of host
 *  encoding.
 *  x - number to write
 *  fp - the stream
 *  bigendian - set to write big bytes first, elee write litle bytes
 *              first
 *  Returns: 0 or EOF on error
 *  Notes: different NaN types and negative zero not preserved.
 *         if the number is too big to represent it will become infinity
 *         if it is too small to represent it will become zero.
 */
int MAT_file::MATLAB_fwriteieee754(double d_value, int big_endian)
{
    int shift;
    unsigned long sign, exp, hibits, hilong, lowlong;
    double fnorm, significand;
    int expbits = 11;
    int significandbits = 52;
            
    /* Zero (can't handle signed zero) */
    if (d_value == 0)
    {
        hilong  = 0;
        lowlong = 0;
        goto writedata;
    }
    
    /* Infinity */
    if (d_value > DBL_MAX)
    {
        hilong = 1024+((1<<(expbits-1))-1);
        hilong <<= (31-expbits);
        lowlong = 0;
        goto writedata;
    }
    
    /* -Infinity */
    if (d_value < -DBL_MAX)
    {
        hilong = 1024+((1<<(expbits-1))-1);
        hilong <<= (31-expbits);
        hilong |= (1 << 31);
        lowlong = 0;
        goto writedata;
    }
    
    /* NaN - dodgy because many compilers optimise out this test, but
     *there is no portable isnan() */
    if (d_value != d_value)
    {
      hilong = 1024 + ((1<<(expbits-1)) - 1);
      hilong <<= (31 - expbits);
      lowlong = 1234;
      goto writedata;
    }
    
    /* Get the sign */
    if (d_value < 0)
    {
        sign  = 1;
        fnorm = -d_value;
    }
    else
    {
        sign  = 0;
        fnorm = d_value;
    }
    
    /* Get the normalized form of f and track the exponent */ 
    shift = 0; 
    while (fnorm >= 2.0)
    {
        fnorm /= 2.0;
        shift++;
    }
    while (fnorm < 1.0)
    {
        fnorm *= 2.0;
        shift--;
    }
   
    /* Check for denormalized numbers */
    if (shift < -1022)
    {
        while (shift < -1022)
        {
            fnorm /= 2.0;
            shift++;
        }
        shift = -1023;
    }
    /* Out of range. Set to infinity */
    else if (shift > 1023)
    {
        hilong = 1024+((1<<(expbits-1))-1);
        hilong <<= (31-expbits);
        hilong |= (sign << 31);
        lowlong = 0;
        goto writedata;
    }
    else
    {
        fnorm = fnorm - 1.0; /* take the significant bit off mantissa */
    }
    
    /* calculate the integer form of the significand */
    /* hold it in a  double for now */
    significand = fnorm*((1LL<<significandbits)+0.5f);
    
    /* get the biased exponent */ 
    exp = shift+((1<<(expbits-1))-1); /* shift + bias */ 
    
    /* put the data into two longs (for convenience) */
    hibits = (long) (significand/4294967296);
    hilong = (sign << 31)|(exp<<(31-expbits))|hibits;
    d_value = significand-hibits*4294967296;
    lowlong = (unsigned long)(significand-hibits*4294967296);
    
writedata:
    /* write the bytes out to the stream */
    if (big_endian)
    {
        mat_filename.put((hilong  >> 24) & 0xFF);
        mat_filename.put((hilong  >> 16) & 0xFF);
        mat_filename.put((hilong  >> 8)  & 0xFF);
        mat_filename.put(hilong          & 0xFF);
        mat_filename.put((lowlong >> 24) & 0xFF);
        mat_filename.put((lowlong >> 16) & 0xFF);
        mat_filename.put((lowlong >> 8)  & 0xFF);
        mat_filename.put(lowlong         & 0xFF);
    }
    else
    {
        mat_filename.put(lowlong         & 0xFF);
        mat_filename.put((lowlong >> 8)  & 0xFF);
        mat_filename.put((lowlong >> 16) & 0xFF);
        mat_filename.put((lowlong >> 24) & 0xFF);
        mat_filename.put(hilong          & 0xFF);
        mat_filename.put((hilong  >> 8)  & 0xFF);
        mat_filename.put((hilong  >> 16) & 0xFF);
        mat_filename.put((hilong  >> 24) & 0xFF);
    }

return EXIT_SUCCESS;
}